package gbench.sandbox.tuple;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.junit.jupiter.api.Test;

import gbench.common.tree.LittleTree.IRecord;
import static gbench.common.tree.LittleTree.IRecord.*;
import gbench.sandbox.tuple.JunitTrie.TrieNode;

public class JunitTokenize {
    
    /**
     * 对 一个  line 进行分词
     * @param line 数据字符串
     * @param keywords 关键词列表
     * @return 分词结果 {name:分词符号,flag:分词标记是 keyword为true否自false,offset:偏移量}
     */
    public static List<IRecord> tokenize(final String line, final List<String> keywords) {
        final var len = line.length();// 模版字符串的长度。
        final Function<String,List<Character>> lc = strline -> strline.chars().mapToObj(c->(char)c)
            .collect(Collectors.toList());// 把一个字符串转换成一个字符列表。List of Characters
        final var keywordsTrie = new TrieNode<Character>('/');// 前缀树,根节点为 '/'
        keywords.stream().map(lc).forEach(keywordsTrie::addWord);// 前缀树
        final var stack = new Stack<String>();// 候选集合：即当前业已发现匹配的 候选placeholder集合。 最长的候选placeholder位于栈顶最短的位于栈底。
        int i = 0;// 当前阶段/周期  读取的模版字符位置。即开始位置。
        final var tokens = new LinkedList<IRecord>(); // 分词结果的集合。
        
        while( i<len ) {// 逐字读取
            stack.clear();// 清空候选集合
            int j = i;// 读取串的结束位置(不包含)。阶段/周期 开始位置 开始。读取串由区间[i,j)来进行定义。
            var prefixNode = keywordsTrie.getNode(Arrays.asList('/',line.charAt(i)));// 前缀节点:i周期的 初始前缀节点。
            
            // 在i周期 初始前缀节点 的基础之上,使用 while循环 逐渐 增加字符 使之成为keyword(最长)以提取keyword.
            while( prefixNode!=null ) {// 尽力的向前读取字符直到不再是前缀为止:尝试在从当前位置开始向后读取占位符，尽可能长的读取字符。直到不能够拼成占位付的前缀为止。
                if(prefixNode.flag()) stack.push(prefixNode.path());
                if(j==len) break;
                prefixNode = keywordsTrie.getNode(lc.apply("/"+line.substring(i, ++j)));// 移动读取位置的右侧索引位置。
            }//while prefixNode!=null
            
            if( !stack.empty() ) { // 已经发现了keyword
                final var keyword = stack.pop();// 提取最长的前缀 即栈顶元素 作为keyword。
                tokens.add(REC("name",keyword,"flag",true,"offset",i)); // 为防止 两个连续keyword,不能把keyword后面的读取的字符视为普通word即flag为false的字符串。
                i+=keyword.length();// 步长移动keyword的长度。
            }else { // word template的子串[i,j)表示模版内容则给予拷贝buffer缓存,即比keywords的前缀多了一个 非前缀字符 的字符串。
                final var word = line.substring(i,j==i?i+1:j);// 读取的单词,因为在  while( prefixNode!=null ) 跳出循环前做过 ++j,所以这里只用使用j
                tokens.add(REC("name",word,"flag",false,"offset",i));// 提取业已读取的(j位置的字符已经读取）字符串长度。
                i+=word.length();// 步长移动到word的长度
            }// !stack.empty()
            
        }// while i<len
        
        return tokens; // 返回结果
    }// fill_template
    
    @Test
    public void foo() {
        final var line = "this is a good story";
        final var keywords = REC("good","bad","story","story writer!");
        final var tk = tokenize(line,keywords.keys());
        tk.forEach(e->{
            System.out.println(e+"--->"+e.str("name").length());
        });
        final var ret = tk.stream().map(e->e.bool("flag")?keywords.str(e.str("name")):e.str("name")).collect(Collectors.joining());
        System.out.println(ret);
    }

}
